package jalgebrava.group.morphisms;

import jalgebrava.group.Group;
import jalgebrava.util.F;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

public class Automorphism<G> extends F<G,G> {
	private static final Logger logger = Logger.getLogger("Automorphism");
	
	private final Group<G> group;
	private final Map<G,G> map;
	private Automorphism(Group<G> inGroup, Map<G,G> inMap) {		
		group = inGroup;
		map = inMap;	
	}
	// keyset of map is guaranteed to generate g
	// map goes between elements of same order
	// return null if no automorphism can be found.
	public final static <G> boolean isAutomorphismMap(Group<G> g, Map<G,G> map) {
		Set<G> valueSet = new HashSet<G>();
		valueSet.addAll(map.values());
		if (map.keySet().size() != g.size() || valueSet.size() != g.size()) {
			return false;
		}
		for (G x: map.keySet()) {
			for (G y: map.keySet()) {
				G f_xy = map.get(g.mul(x, y));
				G fx_fy = g.mul(map.get(x), map.get(y));
				if (!g.equals(f_xy, fx_fy)) {
					return false;
				}
			}
		}
		return true;
	}
	public final static <G> Automorphism<G> extendPartialMap(Group<G> g, Map<G,G> map) {
		
		Map<G,G> result = new HashMap<G,G>();
		result.put(g.unit(), g.unit());
		for (G x: map.keySet()) {
			G y = map.get(x);
			G xi = g.unit();
			G yi = g.unit();
			for (int i=0;i<g.order(x);i++) {
				result.put(xi,yi);
				xi = g.mul(xi, x);
				yi = g.mul(yi, y);
			}
		}
		
		boolean growing = true;
		while (growing) {
			logger.debug(result);
			growing = false;
			Set<G> start = new HashSet<G>(result.keySet()); 
			for (G x: start) {
				G mx = result.get(x);
				
				for (G y: start) {					
					G my = result.get(y);
					G old = result.put(g.mul(x,y), g.mul(mx,my));
					if (old == null) {
						growing = true;
					} else {
						if (!g.equals(old, g.mul(mx,my))) {
							return null;
						}
					}
				}
			}
		}
		if (isAutomorphismMap(g, result)) {
			return new Automorphism<G>(g, result);
		}
		return null;
	}
	
	
	@Override	
	public String toString() {
		
		return map.toString();
	}
	public final static <G> Automorphism<G> identityAutomorphism(Group<G> g) {
		Map<G,G> map = new HashMap<G,G>(g.size());
		for (G x: g) {
			map.put(x,x);
		}
		return new Automorphism<G>(g, map);
	}
	public final Automorphism<G> invert() {
		Map<G,G> result = new HashMap<G,G>(group.size());
		for (G x: group) {
			result.put(this.map.get(x),x);
		}
		return new Automorphism<G>(group, result);
	}
	public final Automorphism<G> multiply(Automorphism<G> other) {
		Map<G,G> result = new HashMap<G,G>(group.size());
		for (G x: group) {
			result.put(x,this.map.get(other.map.get(x)));
		}
		return new Automorphism<G>(group, result);		
	}
	@Override
	public int hashCode() {
		return map.hashCode();
	}
	@Override
	public boolean equals(Object obj) {
		if (obj == null || !(obj instanceof Automorphism)) {
			return false;
		}
		Automorphism<?> rawOther = (Automorphism<?>) obj;
		if (rawOther.group != this.group) {
			return false;			
		}
		Automorphism<G> other = (Automorphism<G>) rawOther;
		for (G x: this.group) {
			if (!this.group.equals(this.map.get(x), other.map.get(x))) {
				return false;
			}
		}
		return true;
	}
	@Override
	public G apply(G a) {
		return map.get(a);
	}
	
}
